package com.autumn.workflow.command.task;

import java.util.Map;

import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.delegate.event.ActivitiEventDispatcher;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntityManager;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.activiti.engine.task.DelegationState;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;

import com.autumn.platform.core.Utils;
import com.autumn.platform.core.exception.Throws;
import com.autumn.workflow.WorkflowConsts;
import com.autumn.workflow.command.ParamCommand;
import com.autumn.workflow.command.task.FreedomTaskCompleteCommand.FreedomTaskCompleteParam;
import com.autumn.workflow.model.UserTaskFlow;

import lombok.Getter;
import lombok.Setter;

public class FreedomTaskCompleteCommand implements ParamCommand<FreedomTaskCompleteParam, Void> {

    @Autowired
    private UserTaskRepository repository;

    @Override
    public Void execute(CommandContext commandContext, FreedomTaskCompleteParam param) {
        // 任务对象
        TaskEntity task = this.getTaskEntity(commandContext, param.getTaskId());
        String srcTaskKey = task.getTaskDefinitionKey();
        String destTaskKey = param.getDestTaskKey();
        if (Utils.isBlank(destTaskKey) || WorkflowConsts.AUTO_TASK_KEY.equalsIgnoreCase(destTaskKey)) {
            // 如果目标节点为空，或者是auto，则调用引擎原有API完成任务
            commandContext.getProcessEngineConfiguration().getTaskService().complete(param.getTaskId(), param.getVariables());
        } else {
            Process process = ProcessDefinitionUtil.getProcess(task.getProcessDefinitionId());
            // 起始节点
            FlowNode src = (FlowNode) process.getFlowElement(srcTaskKey);
            // 目标节点
            FlowNode target = resolveTargetNode(destTaskKey, process);
            // 起始节点和目标节点间的连线（可能为空）
            SequenceFlow sequenceFlow = resolveSequenceFlow(src, destTaskKey);
            if (null == sequenceFlow && null == this.getUserTaskFlow(task, param)) {//如果不存在连线，再从辅助表检查是否允许流转
                String path = new StringBuilder()//
                        .append(srcTaskKey).append(" ==> ").append(destTaskKey)//
                        .append("(procDefId==\"").append(task.getProcessDefinitionId()).append("\"")//
                        .append(", action==").append(param.getAction()).append(")").toString();
                Throws.throwException(WorkflowConsts._500006, path);
            }

            ExecutionEntity executionEntity = commandContext.getExecutionEntityManager().findById(task.getExecutionId());
            // 删除当前任务
            String deleteReason = new StringBuilder()//
                    .append("freedom:").append(src.getName()).append("[").append(srcTaskKey).append("]==>")//
                    .append(target.getName()).append("[").append(destTaskKey).append("]")//
                    .append("(action==").append(param.getAction()).append(")").toString();
            //taskService.deleteTask(task.getId(), deleteReason);
            this.deleteTask(commandContext, task, deleteReason);
            commandContext.getHistoryManager().recordActivityEnd(executionEntity, deleteReason);

            // 推进流程
            executionEntity.setCurrentFlowElement(target);
            Context.getAgenda().planContinueProcessOperation(executionEntity);
        }
        // 在历史中记录任务备注
        this.recordTaskRemark(commandContext, task.getId(), param.getRemark());
        return null;
    }

    private void recordTaskRemark(CommandContext commandContext, String taskId, String remark) {
        if (Utils.isBlank(remark)) {
            return;
        }

        HistoricTaskInstanceEntityManager manager = commandContext.getHistoricTaskInstanceEntityManager();
        HistoricTaskInstanceEntity task = manager.findById(taskId);

        task.setDescription(remark);
        manager.update(task);
    }

    /**
     * 获取中间连线（可能为空）
     * 
     * @param src
     * @param destTaskKey
     * @return
     */
    private SequenceFlow resolveSequenceFlow(FlowNode src, String destTaskKey) {
        if (null != src.getOutgoingFlows()) {
            for (SequenceFlow out : src.getOutgoingFlows()) {
                if (destTaskKey.equals(out.getTargetRef())) {
                    return out;
                }
            }
        }
        return null;
    }

    /**
     * 获取目标节点
     * 
     * @param destTaskKey
     * @param process
     * @return
     */
    private FlowNode resolveTargetNode(String destTaskKey, Process process) {
        FlowElement element = process.getFlowElement(destTaskKey);
        if (null == element) {
            Throws.throwException(WorkflowConsts._500007, destTaskKey);
        } else if (!(element instanceof FlowNode)) {
            Throws.throwException(WorkflowConsts._500008, element.getName() + "[" + destTaskKey + "]");
        }
        // 目标节点
        return (FlowNode) element;
    }

    private void deleteTask(CommandContext commandContext, TaskEntity taskEntity, String deleteReason) {
        // Task delete logic
        if (taskEntity.getDelegationState() != null && taskEntity.getDelegationState().equals(DelegationState.PENDING)) {
            throw new ActivitiException("A delegated task cannot be completed, but should be resolved instead.");
        }

        commandContext.getProcessEngineConfiguration().getListenerNotificationHelper().executeTaskListeners(taskEntity, TaskListener.EVENTNAME_COMPLETE);
        if (Authentication.getAuthenticatedUserId() != null && taskEntity.getProcessInstanceId() != null) {
            ExecutionEntity processInstanceEntity = commandContext.getExecutionEntityManager().findById(taskEntity.getProcessInstanceId());
            commandContext.getIdentityLinkEntityManager().involveUser(processInstanceEntity, Authentication.getAuthenticatedUserId(), IdentityLinkType.PARTICIPANT);
        }

        ActivitiEventDispatcher eventDispatcher = Context.getProcessEngineConfiguration().getEventDispatcher();
        if (eventDispatcher.isEnabled()) {
            eventDispatcher.dispatchEvent(ActivitiEventBuilder.createEntityEvent(ActivitiEventType.TASK_COMPLETED, taskEntity));
        }
        commandContext.getTaskEntityManager().deleteTask(taskEntity, deleteReason, false, false);
    }

    /**
     * 获取任务对象
     * 
     * @param commandContext
     * @param taskId
     * @return
     */
    private TaskEntity getTaskEntity(CommandContext commandContext, String taskId) {
        if (taskId == null) {
            throw new ActivitiIllegalArgumentException("taskId is null");
        }
        TaskEntity task = commandContext.getTaskEntityManager().findById(taskId);
        if (task == null) {
            throw new ActivitiObjectNotFoundException("Cannot find task with id " + taskId, Task.class);
        }
        if (task.isSuspended()) {
            throw new ActivitiException("Cannot execute operation: task is suspended");
        }
        return task;
    }

    /**
     * 获取扩展的任务流
     * 
     * @param task
     * @param param
     * @return
     */
    private UserTaskFlow getUserTaskFlow(TaskEntity task, FreedomTaskCompleteParam param) {
        UserTaskFlow flow = new UserTaskFlow();
        flow.setProcDefId(task.getProcessDefinitionId());
        flow.setSrcTaskKey(task.getTaskDefinitionKey());
        flow.setDestTaskKey(param.getDestTaskKey());
        flow.setAction(param.getAction());
        return repository.queryUserTaskFlow(flow);
    }

    @Getter
    @Setter
    public static class FreedomTaskCompleteParam {

        private String taskId;

        private Map<String, Object> variables;

        private String userId;

        private int action;

        private String destTaskKey;

        private String remark;
    }
}
